﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xceed.Silverlight.Data.Collections;

namespace Xceed.Silverlight.Data
{
  internal sealed class PeekResult<T> : DataEnumeratorResult
  {
    #region Constructor

    internal PeekResult(
      SourcePosition sourcePosition,
      bool approximateSkip,
      long expectedSkipCount,
      int expectedPeekCount,
      Nullable<long> actualSkipCount,
      ICollection<T> peekedItems,
      SourcePosition endPosition,
      RelativeOffset endPositionOffset,
      EstimateInt64 totalCount )
    {
      if( sourcePosition == null )
        throw new ArgumentNullException( "sourcePosition" );

      if( endPosition == null )
        throw new ArgumentNullException( "endPosition" );

      m_sourcePosition = sourcePosition;
      m_endPosition = endPosition;

      m_approximateSkip = approximateSkip;
      m_expectedSkipCount = expectedSkipCount;
      m_expectedPeekCount = expectedPeekCount;
      m_actualSkipCount = actualSkipCount;

      m_endPositionOffset = endPositionOffset;
      m_totalCount = totalCount;

      if( peekedItems != null )
      {
        m_peekedItems = peekedItems.AsImmutable();
      }
      else
      {
        m_peekedItems = null;
      }

      this.Coerce();
      this.Validate();
    }

    #endregion

    #region SourcePosition Property

    internal SourcePosition SourcePosition
    {
      get
      {
        return m_sourcePosition;
      }
    }

    private readonly SourcePosition m_sourcePosition;

    #endregion

    #region EndPositionObject Property

    internal SourcePosition EndPositionObject
    {
      get
      {
        return m_endPosition;
      }
    }

    private readonly SourcePosition m_endPosition;

    #endregion

    #region ActualSkipCount Property

    internal Nullable<long> ActualSkipCount
    {
      get
      {
        return m_actualSkipCount;
      }
    }

    private readonly Nullable<long> m_actualSkipCount;

    #endregion

    #region ApproximateSkip Property

    internal bool ApproximateSkip
    {
      get
      {
        return m_approximateSkip;
      }
    }

    private readonly bool m_approximateSkip;

    #endregion

    #region ExpectedSkipCount Property

    internal long ExpectedSkipCount
    {
      get
      {
        return m_expectedSkipCount;
      }
    }

    private readonly long m_expectedSkipCount;

    #endregion

    #region PeekedItems Property

    internal ICollection<T> PeekedItems
    {
      get
      {
        return m_peekedItems;
      }
    }

    private readonly ImmutableCollection<T> m_peekedItems;

    #endregion

    #region ExpectedPeekCount Property

    internal int ExpectedPeekCount
    {
      get
      {
        return m_expectedPeekCount;
      }
    }

    private readonly int m_expectedPeekCount;

    #endregion

    #region Result Property

    internal bool Result
    {
      get
      {
        return ( m_peekedItems != null ) 
          && ( m_peekedItems.Count == Math.Abs( this.ExpectedPeekCount ) );
      }
    }

    #endregion

    #region EndPositionOffset Property

    internal RelativeOffset EndPositionOffset
    {
      get
      {
        return m_endPositionOffset;
      }
    }

    private RelativeOffset m_endPositionOffset;

    #endregion

    #region TotalCount Property

    internal EstimateInt64 TotalCount
    {
      get
      {
        return m_totalCount;
      }
    }

    private EstimateInt64 m_totalCount;

    #endregion

    internal static PeekResult<T> Failure( PeekParameters parameters )
    {
      return new PeekResult<T>(
        parameters.SourcePosition,
        parameters.ApproximateSkip,
        parameters.ExpectedSkipCount,
        parameters.ExpectedPeekCount,
        null,
        null,
        parameters.SourcePosition,
        null,
        null );
    }

    private void Validate()
    {
      if( ( m_peekedItems != null ) && ( m_actualSkipCount != m_expectedSkipCount ) )
        throw new DataInternalException();

      if( ( ( m_endPositionOffset != null ) && !m_endPositionOffset.IsEstimate && ( m_endPositionOffset.Value < 0 ) )
        || ( ( m_totalCount != null ) && !m_totalCount.IsEstimate && ( m_totalCount.Value < 0 ) ) )
        throw new DataInternalException();

      if( ( m_totalCount != null ) && !m_totalCount.IsEstimate )
      {
        var span = this.GetDisplacementSpan();
        if( span.HasValue && ( m_totalCount.Value < span - 1 ) )
          throw new DataInternalException();

        if( ( m_endPositionOffset != null ) && !m_endPositionOffset.IsEstimate && ( m_endPositionOffset.Value > m_totalCount.Value + 1 ) )
          throw new DataInternalException();
      }
    }

    private void Coerce()
    {
      if( !m_actualSkipCount.HasValue && this.Result )
        return;

      var beforeFirstOffset = ( ( m_endPosition != null ) && m_endPosition.IsBeforeFirst ) ? ( long? )0 : null;
      var afterLastOffset = ( ( m_endPosition != null ) && m_endPosition.IsAfterLast ) ? ( long? )0 : null;

      if( ( !m_approximateSkip )
        && ( m_sourcePosition != null )
        && ( m_sourcePosition.IsOutOfBound )
        && ( m_sourcePosition != m_endPosition )
        && ( m_actualSkipCount.HasValue ) )
      {
        long fetchDisplacement = 0;

        if( ( m_peekedItems != null ) && ( m_peekedItems.Count > 0 ) )
        {
          fetchDisplacement = m_peekedItems.Count;

          if( m_endPosition != null && !m_endPosition.IsOutOfBound )
          {
            fetchDisplacement--;
          }

          fetchDisplacement *= DataUtils.GetUnit( m_expectedPeekCount );
        }

        long offset = Math.Abs( m_actualSkipCount.Value + fetchDisplacement );

        if( m_sourcePosition.IsBeforeFirst )
        {
          Debug.Assert( ( m_endPosition == null ) || !m_endPosition.IsBeforeFirst || ( offset == 0 ) );

          beforeFirstOffset = offset;
        }
        else if( m_sourcePosition.IsAfterLast )
        {
          Debug.Assert( ( m_endPosition == null ) || !m_endPosition.IsAfterLast || ( offset == 0 ) );

          afterLastOffset = offset;
        }
      }

      if( beforeFirstOffset.HasValue && afterLastOffset.HasValue )
      {
        long totalCount = beforeFirstOffset.Value + afterLastOffset.Value - 1;

        if( ( m_totalCount != null ) && !m_totalCount.IsEstimate )
        {
          if( m_totalCount != totalCount )
            throw new DataInternalException();
        }
        else
        {
          m_totalCount = totalCount;
        }
      }

      if( beforeFirstOffset.HasValue || afterLastOffset.HasValue )
      {
        if( ( m_endPositionOffset != null ) && !m_endPositionOffset.IsEstimate )
        {
          if( ( m_endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst ) && beforeFirstOffset.HasValue )
          {
            if( m_endPositionOffset.Value != beforeFirstOffset.Value )
              throw new DataInternalException();
          }
          else if( ( m_endPositionOffset.ReferencePosition == ReferencePoint.AfterLast ) && afterLastOffset.HasValue )
          {
            if( m_endPositionOffset.Value != afterLastOffset.Value )
              throw new DataInternalException();
          }
          else
          {
            if( ( m_totalCount != null ) && !m_totalCount.IsEstimate )
            {
              long offset = ( m_endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                              ? afterLastOffset.Value
                              : beforeFirstOffset.Value;

              if( m_endPositionOffset.Value != ( m_totalCount.Value - offset + 1 ) )
                throw new DataInternalException();
            }
          }
        }
        else
        {
          if( beforeFirstOffset != null )
          {
            m_endPositionOffset = new RelativeOffset( beforeFirstOffset.Value, ReferencePoint.BeforeFirst );
          }
          else
          {
            m_endPositionOffset = new RelativeOffset( afterLastOffset.Value, ReferencePoint.AfterLast );
          }
        }
      }
    }

    private Nullable<long> GetDisplacementSpan()
    {
      Nullable<long> value = null;

      if( m_peekedItems != null )
      {
        value = Math.Abs( m_expectedSkipCount );

        int itemCount = m_peekedItems.Count;
        if( itemCount > 0 )
        {
          if( DataUtils.GetUnit( m_expectedSkipCount ) == DataUtils.GetUnit( m_expectedPeekCount ) )
          {
            value += itemCount - 1;
          }
          else
          {
            value = Math.Max( value.Value, itemCount );
          }
        }
      }
      else if( m_actualSkipCount.HasValue )
      {
        value = Math.Abs( m_actualSkipCount.Value );
      }

      return value;
    }
  }
}
